<?php
	if (!is_dir(DIR_MODELS_EAV)) {
        die("DIR_MODELS_EAV is not defined.");
    }
    
    class WZ_Eav
	{
        /*
            // MODELE EXEMPLE
            params[] = nom
            params[] = pays
            params[] = prix
            params[] = stock

            types[nom] = varchar
            types[pays] = varchar
            types[prix] = decimal
            types[stock] = int

            required[nom] = true
            required[pays] = true
            required[prix] = true
            required[stock] = true

            content[nom] = self
            content[pays] = self
            content[prix] = self
            content[stock] = self
            
            // EXEMPLE D UTILISATION
            
            $eav = WZ_Eav::getInstance('test');
            $produits = $eav->select('produits', "prix > 10000 || pays LIKE '%a%'", 'pays', 'asc', 1);
            $produits = $eav->select('produits');
            $produit = $eav->create('produits');
            $produit = $eav->selectByEntity(1);
            $produit->prix = 18;
            $produit->stock = 25;
            $produit->pays = 'usa';
            $produit->save();
            var_dump($produit);
            $produit->delete();
            var_dump($produit);
            var_dump($produits);
            exit;
        */
        
        public $_db;
        public $_errors;
        public $_base;
        public $_table;
        public $_dirModel;
        public $_model;
        public $_param;
        public $_paramType;
        public $_paramRequired;
        public $_paramContent;
        public $_value;
        public $_entity;
        public $_entityExists;
        public $_force;
        public $_obj;
        static $_instance;
        
        // singleton
		public static function &getInstance($base) 
		{
			if (!self::$_instance instanceof self) {
				self::$_instance = new self($base);
			}
            return self::$_instance;
		}
        
		public function __construct($base) 
		{
            $this->_db = \WZ::get('ZendDb');
            $q = "SELECT id_base FROM eav_base WHERE value = '" . addslashes($base) . "'";
            $res = $this->_db->fetchRow($q);
            if (false !== $res) {
                $this->_base = $res['id_base'];
            } else {
                $q = "INSERT INTO eav_base SET value = '" . addslashes($base) . "'";
                $this->_db->query($q);
                $this->_base = $this->_db->lastInsertId();
			}
            $this->_dirModel = DIR_MODELS_EAV . $base . '/';
            if (!is_dir($this->_dirModel)) {
                return false;
            }
		}
        
        public function setTable($table)
        {
            $q = "SELECT id_table FROM eav_table WHERE value = '" . addslashes($table) . "' && id_base = '" . $this->_base . "'";
            $res = $this->_db->fetchRow($q);
            if (false !== $res) {
                $this->_table = $res['id_table'];
            } else {
                $q = "INSERT INTO eav_table SET value = '" . addslashes($table) . "', id_base = '" . $this->_base . "'";
                $this->_db->query($q);
                $this->_table = $this->_db->lastInsertId();
			}
            $this->setModel($table);
        }
        
        public function setModel($model)
        {
			$model = ucfirst(strtolower(\WZ_Inflector::clean($model)));
            if (!file_exists($this->_dirModel . $model . '.ini')) {
                return false;
            }
            $model = new \Zend_Config_Ini($this->_dirModel . $model . '.ini');
            $params = $model->params->toArray();
            $types = $model->types->toArray();
            $required = $model->required->toArray();
            $content = $model->content->toArray();
            $this->_model = array(
                'params' => $params,
                'types' => $types,
                'content' => $content,
                'required' => $required
            );
        }
        
        public function setParam($param)
        {
            $this->_paramName = $param;
            $this->_paramType = $this->_model['types'][$param];
            $this->_paramRequired = $this->_model['required'][$param];
            $this->_paramContent = $this->_model['content'][$param];
            
            $q = "SELECT id_param FROM eav_param WHERE value = '" . addslashes($param) . "' && id_table = '" . $this->_table . "' && param_type = '" . $this->_paramType . "'";
            $res = $this->_db->fetchRow($q);
            if (false !== $res) {
                $this->_param = $res['id_param'];
            } else {
                $q = "INSERT INTO eav_param SET param_type = '" . $this->_paramType . "', value = '" . addslashes($param) . "', id_table = '" . $this->_table . "'";
                $this->_db->query($q);
                $this->_param = $this->_db->lastInsertId();
			}
        }
        
        public function setValue($value)
        {
            $q = "SELECT eav_value.id_value FROM eav_value LEFT JOIN eav_param ON eav_value.id_param = eav_param.id_param WHERE eav_param.param_type = '" . $this->_paramType . "' && eav_value.value = '" . addslashes($value) . "' && eav_value.id_param = '" . $this->_param . "' && eav_value.id_entity = '" . $this->_entity . "'";
            $res = $this->_db->fetchRow($q);
            if (false !== $res && false === $this->_force) {
                $this->_value = $res['id_value'];
            } else {
                $q = "INSERT INTO eav_value SET value = '" . addslashes($value) . "', id_param = '" . $this->_param . "', id_entity = '" . $this->_entity . "'";
                $this->_db->query($q);
                $this->_value = $this->_db->lastInsertId();
			}
        }
        
        public function setEntity($data = array())
        {
            $idEntity = null;
            if (false === $this->_entityExists && false === $this->_force) {
                if (count($data)) {
                    $first = true;
                    foreach ($data as $param => $value) {
                        $this->setParam($param);
                        $q = "SELECT eav_value.id_entity FROM eav_value LEFT JOIN eav_param ON eav_value.id_param = eav_param.id_param WHERE eav_param.param_type = '" . $this->_paramType . "' && eav_value.id_param = '" . addslashes($this->_param) . "' && eav_value.value = '" . addslashes($value) . "'";
                        $res = $this->_db->fetchAll($q);
                        $count = count($res);
                        if ($count > 0) {
                            $entities = array();
                            foreach ($res as $row) {
                                $idEntity = $row['id_entity'];
                                if (!in_array($idEntity, $entities)) {
                                    $entities[] = $idEntity;
                                }
                            }
                            $checkCount = count($data);
                            $entityCount = array();
                            foreach ($entities as $entity) {
                                $entityCount[$entity] = 0;
                                foreach ($data as $param => $value) {
                                    $this->setParam($param);
                                    $qCheck = "SELECT eav_value.id_value FROM eav_value LEFT JOIN eav_param ON eav_value.id_param = eav_param.id_param WHERE eav_param.param_type = '" . $this->_paramType . "' && eav_value.id_param = '" . addslashes($this->_param) . "' && eav_value.id_entity = $entity && eav_value.value = '" . addslashes($value) . "'";
                                    $tmpRes = $this->_db->fetchRow($qCheck);
                                    $tmpCount = (false !== $tmpRes) ? 1 : 0;
                                    if ($tmpCount > 0) {
                                        $entityCount[$entity]++;
                                    }
                                }
                                if ($entityCount[$entity] == $checkCount) {
                                    $this->_entityExists = true;
                                    $this->_entity = $idEntity;
                                    break;
                                }
                            }
                            if (isset($res['id_entity'])) {
                                if (false === $first) {
                                    if ($idEntity != $res['id_entity']) {
                                        $this->setEntity();
                                        break;
                                    } else {
                                        $idEntity = $res['id_entity'];
                                    }
                                } else {
                                    $idEntity = $res['id_entity'];
                                }
                            }
                        } else {
                            $this->setEntity();
                            break;
                        }
                        $first = false;
                    }
                    if (null !== $idEntity) {
                        $this->_entityExists = true;
                        $this->_entity = $idEntity;
                    }
                } else {
                    $q = "INSERT INTO eav_entity SET id_table = '" . $this->_table . "'";
                    $this->_db->query($q);
                    $this->_entityExists = true;
                    $this->_force = true;
                    $this->_entity = $this->_db->lastInsertId();
                }
            }
        }
        
        public function getValueById($id)
        {
            $q = "SELECT value FROM eav_value WHERE id_value = $id";
            $res = $this->_db->fetchRow($q);
            $count = (false !== $res) ? 1 : 0;
            if ($count > 0) {
                return $res['value'];
            }
            return null;
        }
        
        public function set($param, $value)
        {
            $param = '_' . $param;
            $this->$param = $value;
        }
        
        public function get($param)
        {
            $param = '_' . $param;
            if (isset($this->$param)) {
                return $this->$param;
            }
            return null;
        }
        
        public function add($table, $data = array())
        {
            $this->setTable($table);
            $this->_entityExists = false;
            $this->_force = false;
            $this->setEntity($data);
            if (true === $this->_force) {
                $q = "SELECT id_entity FROM eav_entity ORDER BY id_entity DESC LIMIT 1";
                $res = $this->_db->fetchRow($q);
                $this->_entity = $res['id_entity'];
            }
            if (count($data)) {
                foreach ($data as $param => $value) {
                    $this->setParam($param);
                    $this->setValue($value);
                }
            }
            return $this->_entity;
        }
        
        public function update($idEntity, $data)
        {
            if (count($data)) {
                foreach ($data as $param => $value) {
                    $this->setParam($param);
                    $q = "UPDATE eav_value SET value = '" . addslashes($value) . "' WHERE id_param = '" . $this->_param . "' && id_entity = $idEntity";
                    $this->_db->query($q);
                }
            }
            return $idEntity;
        }
        
        public function delete($idEntity)
        {
            $q = "DELETE FROM eav_entity WHERE id_entity = $idEntity";
            $this->_db->query($q);
            
            $q = "DELETE FROM eav_value WHERE id_entity = $idEntity";
            $this->_db->query($q);
        }
        
        public function create($table)
        {
            $this->setTable($table);
            $this->_obj = new \WZ_Eavmodel;
            $this->_obj->_tableRef = $this->_table;
            $this->_obj->_baseRef = $this->_base;
            return $this->_obj;
        }
        
        public function save()
        {
            $allParams = $this->_model['params'];
            $vars = get_object_vars($this->_obj);
            $update = false;
            $data = array();
            foreach ($vars as $param => $value) {
                if ($param == 'idEntity') {
                    $update = true;
                    $idEntity = $value;
                } else {
                    if (!is_object($value) && in_array($param, $allParams)) {
                        $data = array_merge($data, array($param => utf8_decode($value)));
                    }
                }
            }
            
            foreach ($allParams as $param) {
                if (!isset($data[$param]) && false === $update) {
                    $require = $this->_model['required'][$param];
                    if (true == $require) {
                        $this->_error("$param is required.");
                    }
                    $data[$param] = null;
                }
            }
            if (count($this->_errors)) {
                var_dump($this->_errors);
                return false;
            }
            if (false === $update) {
                $table = $this->getTableById($this->_table);
                $idEntity = $this->add($table, $data);
            } else {
                $this->_entity = $idEntity;
                $idEntity = $this->update($idEntity, $data);
            }
            return $this->selectByEntity($idEntity);
        }
        
        public function _error($str)
        {
            $this->_errors[] = $str;
        }
        
        public static function getTableById($idTable)
        {
            $db = \WZ::get('ZendDb');
            $q = "SELECT value FROM eav_table WHERE id_table = $idTable";
            $res = $db->fetchRow($q);
            $count = (false !== $res) ? 1 : 0;;
            if ($count > 0) {
                return $res['value'];
            }
            return false;
        }
       
        
        public static function getBaseById($idBase)
        {
            $db = \WZ::get('ZendDb');
            $q = "SELECT value FROM eav_base WHERE id_base = $idBase";
            $res = $db->fetchRow($q);
            $count = (false !== $res) ? 1 : 0;
            if ($count > 0) {
                return $res['value'];
            }
            return false;
        }
        
        public function selectByEntity($idEntity)
        {
            $db = \WZ::get('ZendDb');
            $q = "SELECT eav_param.value AS param, eav_param.id_table AS tableid, eav_value.value AS value FROM eav_value LEFT JOIN eav_param ON eav_value.id_param = eav_param.id_param WHERE eav_value.id_entity = $idEntity";
            $res = $db->fetchAll($q);
            $count = count($res);
            if ($count > 0) {
                $obj = new \WZ_Eavmodel;
                $obj->idEntity = $idEntity;
                foreach ($res as $row) {
                    extract($row);
                    $obj->$param = $value;
                    $obj->_tableRef = $tableid;
                    $this->_table = $tableid;
                }
                $obj->_baseRef = $this->_base;
                return $obj;
            }
            return null;
        }
        
        public function deleteWhere($table, $conditions)
        {
            $records = $this->select($table, $conditions);
            if (count($records)) {
                foreach ($records as $idEntity => $record) {
                    $entity = $this->selectByEntity($idEntity);
                    $entity->delete();
                }            
            }
        }
        
        public function updateWhere($table, $conditions, $data)
        {
            $records = $this->select($table, $conditions);
            if (count($records) && count($data)) {
                foreach ($records as $idEntity => $record) {
                    $entity = $this->selectByEntity($idEntity);
                    foreach ($data as $param => $value) {
                        $entity->$param = $value;
                    }
                    $entity->save();
                }            
            }
        }
        
        public function select($table, $conditions = null, $orderBy = null, $order = 'ASC', $limit = null, $offset = 0)
        {
            $this->setTable($table);
            $data = array();
            $q = "SELECT 
            eav_param.value AS param, 
            eav_value.value AS value, 
            eav_param.param_type AS param_type, 
            eav_value.id_entity AS idEntity 
            FROM eav_value 
            LEFT JOIN eav_param ON eav_value.id_param = eav_param.id_param
            WHERE eav_param.id_table = '" . $this->_table . "'";
            
            if (null === $conditions) {
                $res = $this->_db->fetchAll($q);
                $count = count($res);
                if ($count > 0) {
                    foreach ($res as $row) {
                        extract($row);
                        $data[$idEntity][$param] = $value;
                    }
                }
            } else {
                $ids = $this->parseQuery($conditions);
                if (null !== $ids) {
                    $q .= ' && eav_value.id_entity IN (' . $ids . ')';
                    $res = $this->_db->fetchAll($q);
                    $count = count($res);
                    if ($count > 0) {
                        foreach ($res as $row) {
                            extract($row);
                            $data[$idEntity][$param] = $value;
                        }
                    }
                }
            }
            if (count($data)) {
                $this->_db = \WZ::get('ZendDb');
                $nameTableTemp = "tmpobj_$this->_table" . "_" . md5(time() . serialize($data));
                $q = "CREATE TEMPORARY TABLE $nameTableTemp (
                `id_tmp` int(11) unsigned NOT NULL AUTO_INCREMENT,
                `id_entity` int(11) unsigned NOT NULL,";
                
                foreach ($data as $tmpEntity => $row) {
                    foreach ($row as $param => $value) {
                        $type = $this->_model['types'][$param];
                        if ($type == 'text') {
                            $q .= "`$param` text,";
                        } else if ($type == 'varchar') {
                            $q .= "`$param` varchar(255) DEFAULT NULL,";
                        } else if ($type == 'datetime') {
                            $q .= "`$param` datetime DEFAULT NULL,";
                        } else if ($type == 'decimal') {
                            $q .= "`$param` decimal(9,2) unsigned DEFAULT NULL,";
                        } else if ($type == 'int') {
                            $q .= "`$param` int(11) unsigned DEFAULT NULL,";
                        } else {
                            $q .= "`$param` text,";
                        }
                    }
                    break;
                }
                $q .= "PRIMARY KEY (`id_tmp`),
  KEY `id_entity` (`id_entity`))   ENGINE=Memory  DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT";
                $this->_db->query($q);
                foreach ($data as $tmpEntity => $row) {
                    $q = "INSERT INTO $nameTableTemp SET id_entity = $tmpEntity, ";
                    foreach ($row as $param => $value) {
                        $q .= "$param = '" . addslashes($value) . "', ";
                    }
                    $q = substr($q, 0, -2);
                    $this->_db->query($q);
                }
                
                $q = "SELECT * FROM $nameTableTemp";
                $res = $this->_db->fetchAll($q);
                $newData = array();
                foreach ($res as $row) {
                    $idEntity = $row['id_entity'];
                    $newData[$idEntity] = array();
                    foreach ($row as $param => $value) {
                        if ($param != 'id_tmp' && $param != 'id_entity') {
                            $newData[$idEntity][$param] = $value;
                        }
                    }
                }
                $data = $newData;
                
                
                if (null !== $orderBy) {
                    $q = "SELECT * FROM $nameTableTemp ORDER BY $orderBy $order";
                    $res = $this->_db->fetchAll($q);
                    $newData = array();
                    foreach ($res as $row) {
                        $idEntity = $row['id_entity'];
                        $newData[$idEntity] = array();
                        foreach ($row as $param => $value) {
                            if ($param != 'id_tmp' && $param != 'id_entity') {
                                $newData[$idEntity][$param] = $value;
                            }
                        }
                    }
                    $data = $newData;
                }
            
                if (null !== $limit) {
                    $data = array_slice($data, $offset, $limit, true);
                }
                $q = "DROP TEMPORARY TABLE $nameTableTemp";
            }
            return $data;
        }
        
        public function parseQuery($conditions)
        {
            $ids = array();
            $resetQ = $q = "SELECT eav_value.id_entity AS idEntity FROM eav_value LEFT JOIN eav_param ON eav_value.id_param = eav_param.id_param WHERE ";
            if (!strstr($conditions, ' && ') && !strstr($conditions, ' || ')) {
                list($param, $op, $value) = explode(' ', $conditions);
                $this->setParam($param);
                $q .= "(eav_value.id_param = '" . $this->_param . "' && eav_value.value $op $value)";
                $q .= " && eav_param.id_table = '" . $this->_table . "'";
                $res = $this->_db->fetchAll($q);
                
                foreach ($res as $row) {
                    extract($row);
                    if (!in_array($idEntity, $ids)) {
                        $ids[] = $idEntity;
                    }
                }
            }
            $newQuery = $conditions;
            $tabAnd = explode(' && ', $conditions);
            foreach ($tabAnd as $subQuery) {
				$newQuery = str_replace($subQuery . ' && ', '', $newQuery);
			}
			$tabOr = explode(' || ', $newQuery);
            
            if (count($tabAnd) > 1) {
				$resAnd = array();
				foreach ($tabAnd as $subQuery) {
					$q = $resetQ;
                    list($param, $op, $value) = explode(' ', $subQuery);
                    $this->setParam($param);
                    $q .= "(eav_value.id_param = '" . $this->_param . "' && eav_value.value $op $value)";
                    $q .= " && eav_param.id_table = '" . $this->_table . "'";
                    $res = $this->_db->fetchAll($q);
                    $tmp = array();
                    foreach ($res as $row) {
                        extract($row);
                        if (!in_array($idEntity, $tmp)) {
                            $tmp[] = $idEntity;
                        }
                    }
                    if (count($resAnd)) {
                        $intersection = array_intersect($resAnd, $tmp);
                        $resAnd = $intersection;
                    } else {
                        $resAnd = $tmp;
                    }
                }
                $ids = array_merge($ids, $resAnd);
            }
            
            if (count($tabOr) > 1) {
				$resOr = array();
				foreach ($tabOr as $subQuery) {
					$q = $resetQ;
                    list($param, $op, $value) = explode(' ', $subQuery);
                    $this->setParam($param);
                    $q .= "(eav_value.id_param = '" . $this->_param . "' && eav_value.value $op $value)";
                    $q .= " && eav_param.id_table = '" . $this->_table . "'";
                    $res = $this->_db->fetchAll($q);
                    $tmp = array();
                    foreach ($res as $row) {
                        extract($row);
                        if (!in_array($idEntity, $tmp)) {
                            $tmp[] = $idEntity;
                        }
                    }
                    $resOr = array_merge($resOr, $tmp);
                }
                $ids = array_merge($ids, $resOr);
            }

            if (count($ids)) {
                return implode(',', $ids);
            }
            return null;
        }
    }
    
    class WZ_Eavmodel
    {
        public function save()
        {
            $base = WZ_Eav::getBaseById($this->_baseRef);
            $eav = WZ_Eav::getInstance($base);
            $eav->save();
        }
        
        public function delete()
        {
            $base = WZ_Eav::getBaseById($this->_baseRef);
            $eav = WZ_Eav::getInstance($base);
            $eav->delete($this->idEntity);
        }
    }
    